# Author: Robert J. Hijmans
# Date :  October 2017
# Version 1.0
# License GPL v3

new_rast <- function(nrows=10, ncols=10, nlyrs=1, xmin=0, xmax=1, ymin=0, ymax=1, crs, extent, resolution, vals, names, time, units) {

	ncols <- round(ncols)
	if (ncols < 1) error("rast", "ncols < 1")
	nrows <- round(nrows)
	if (nrows < 1) error("rast", "nrows < 1")
	if (nlyrs < 1) error("rast", "nlyrs < 1")

	if (missing(extent)) {
		e <- c(xmin, xmax, ymin, ymax)
	} else {
		extent <- ext(extent)
		e <- as.vector(extent)
	}
	if ((e[1] >= e[2]) || e[3] >= e[4]) {
		error("rast,missing", "invalid extent")
	}
	if (missing(crs)) {
		if (e[1] > -360.01 & e[2] < 360.01 & e[3] > -90.01 & e[4] < 90.01) {
			crs <- "OGC:CRS84"
		} else {
			crs <- ""
		}
	} else {
		crs <- character_crs(crs, "rast")
	}
	#check_proj4_datum(crs)

	r <- methods::new("SpatRaster")
	r@pntr <- SpatRaster$new(c(nrows, ncols, nlyrs), e, crs)
	r <- messages(r, "rast")

	if (!missing(resolution)) {
		res(r) <- resolution
	}
	if (!missing(vals)) {
		if (length(vals) == 1) {
			if (is.na(vals[1])) {
				vals <- as.numeric(NA)
			}
		}
		if (!is.null(ncol(vals))) {
			nms <- colnames(vals)
			if (length(nms) == nlyr(r)) {
				names(r) <- nms
			}
		}
		values(r) <- vals
	}
	if (!missing(names)) {
		names(r) <- names
	}
	if (!missing(time)) {
		time(r) <- time
	}
	if (!missing(units)) {
		time(r) <- units
	}
	r
}


setMethod("rast", signature(x="missing"),
	function(x, nrows=180, ncols=360, nlyrs=1, xmin=-180, xmax=180, ymin=-90, ymax=90, crs, extent, resolution, vals, names, time, units) {
		new_rast(nrows, ncols, nlyrs, xmin, xmax, ymin, ymax, crs, extent, resolution, vals, names, time, units)
	}
)




rast_from_image <- function(x) { 
	# list representing an "image"
	if (! all(dim(x$z) == c(length(x$x), length(x$y)))) {
		error("rast", '"z" does not have the right dimensions') 
	}
	# omitted "-1" bug fix by Barry Rowlingson 
	resx <- ( x$x[length(x$x)] - x$x[1] ) / (length(x$x)-1)
	resy <- ( x$y[length(x$y)] - x$y[1] ) / (length(x$y)-1)
	xmn <- min(x$x) - 0.5 * resx
	xmx <- max(x$x) + 0.5 * resx
	ymn <- min(x$y) - 0.5 * resy
	ymx <- max(x$y) + 0.5 * resy

	dx <- abs(max(abs((x$x[-1] - x$x[-length(x$x)])) / resx) - 1)
	dy <- abs(max(abs((x$y[-1] - x$y[-length(x$y)])) / resy) - 1)
	if (is.na(dx) | is.na(dy)) {
		error("rast", "missing values in coordinates")
	} 
	if (dx > 0.01 | dy > 0.01) {
		error("rast", "data are not on a regular grid")
	}
#	if (xmn > -360.1 & xmx < 360.1 & ymn > -90.1 & ymx < 90.1) { 
#		crs <- "+proj=longlat +datum=WGS84"
#	} else {
#		crs <- ""
#	}
	x <- t(x$z)
	x <- x[nrow(x):1, ]
	rast(x, extent=ext(xmn, xmx, ymn, ymx), crs="")
}



setMethod("rast", signature(x="list"),
	function(x, warn=TRUE) {
		i <- sapply(x, function(i) inherits(i, "SpatRaster"))
		if (!all(i)) {
			if (!any(i)) {
				if ((length(x) == 3) && all(c("x", "y", "z") %in% names(x))) {
					return(rast_from_image(x))
				}
				error("rast,list", "none of the elements of x are a SpatRaster")
			} else {
				warn("rast", sum(!i), " out of ", length(x), " elements of x are not a SpatRaster")
				x <- x[i]
			}
		}
		# start with an empty raster (alternatively use a deep copy)
		out <- deepcopy(x[[1]])
		if (length(x) == 1) {
			return(out)
		}
		opt <- spatOptions()
		for (i in 2:length(x)) {
			out@pntr$addSource(x[[i]]@pntr, warn, opt)
		}
		out <- messages(out, "rast")
		lnms <- names(x)
		if (!is.null(lnms)) {
			if (any(lnms != "") && (length(lnms) == nlyr(out))) {
				rnms <- names(out)
				rnms[lnms != ""] <- lnms[lnms != ""]
				names(out) <- rnms
			} else if (all(lnms != "")) {
				nl <- sapply(x, nlyr)
				rnms <- sapply(1:length(nl), function(i) {
							if (nl[i] > 1) paste0(lnms[i], "_", 1:nl[i]) else lnms[i]
						})
				names(out) <- unlist(rnms)
			}
		}
		out
	}
)


setMethod("rast", signature(x="SpatExtent"),
	function(x, ...) {
		dots <- list(...)
		dots$xmin=x[1]
		dots$xmax=x[2]
		dots$ymin=x[3]
		dots$ymax=x[4]
		r <- do.call(new_rast, dots)
		# re-apply extent to avoid mimute differences
		ext(r) <- x
		r
	}
)


setMethod("rast", signature(x="SpatVector"),
	function(x, type="", ...) {
	
		if (type == "xyz") {
			if (geomtype(x) != "points") {
				error("rast", "xyz can only be used with points")
			}
			x <- data.frame(crds(x), data.frame(x))
			return(rast(x, type="xyz", ...))
		}
	
		dots <- list(...)
		e <- ext(x)
		dots$xmin=e[1]
		dots$xmax=e[2]
		dots$ymin=e[3]
		dots$ymax=e[4]
		if (all(is.na(pmatch(names(dots), "crs")))) {
			dots$crs <- crs(x)
		}
		do.call(new_rast, dots)
	}
)



.fullFilename <- function(x, mustExist=FALSE, vsi=FALSE) {
	x <- trimws(x)
	x <- x[x != ""]
	x <- enc2utf8(x)
	
	i <- substr(x, 1, 5) == "s3://" 
	x[i] <- paste0("/vsis3/", substr(x[i], 6, nchar(x[i])))
	if (all(i)) return(x)
	
	i <- substr(x, 1, 4) == "http" 
	if (vsi) {
		x[i] <- paste0("/vsicurl/", x[i])
	}
	if (all(i)) return(x)

	i <- grepl(":", x)
	if (all(i)) return(x)
	
	p <- normalizePath(x[!i], winslash = "/", mustWork = FALSE)
	if (mustExist) {
		j <- file.exists(dirname(p))
		x[j] <- p[j]
	} else {
		i <- !file.exists(p)
		p[i] <- x[i]
		return(p)
	}
	#if (identical(basename(x), x)) {
	#	x <- file.path(getwd(), x)
	#}
	#if (expand) {
	#	x <- path.expand(x)
	#}
	return(x)
}

clean_domains <- function(domains) {
	if (is.null(domains)) {
		domains <- ""[0]
	} else {
		domains <- unique(domains)
		domains <- as.character(domains[!is.na(domains)])
	}
	domains
}

setMethod("rast", signature(x="character"),
	function(x, subds=0, lyrs=NULL, drivers=NULL, opts=NULL, win=NULL, snap="near", vsi=FALSE, raw=FALSE, noflip=FALSE, domains="", guessCRS=TRUE) {

		f <- .fullFilename(x, vsi=vsi)
		if (length(f) == 0) {
			error("rast", "filename is empty. Provide a valid filename")
		}

		if ((length(f) == 1) && grepl("\\.rds$", tolower(f[1]))) {
			r <- unwrap(readRDS(x))
			if (!inherits(r, "SpatRaster")) {
				error("rast", "the rds file does not store a SpatRaster")
			}
			return(r)
		}

		domains <- clean_domains(domains)
		r <- methods::new("SpatRaster")
		#subds <- subds[1]
		if (is.null(opts)) opts <- ""[0]
		if (raw) opts <- c(opts, "so=false")
		if (is.null(drivers)) drivers <- ""[0]
		if (length(subds) == 0) subds = 0
		if (is.character(subds)) {
			r@pntr <- SpatRaster$new(f, -1, subds, FALSE, drivers, opts, 0[], isTRUE(noflip), isTRUE(guessCRS), domains)
		} else {
			r@pntr <- SpatRaster$new(f, subds-1, "", FALSE, drivers, opts, 0[], isTRUE(noflip), isTRUE(guessCRS), domains)
		}
		r <- messages(r, "rast")
		if (r@pntr$getMessage() == "ncdf extent") {
			# could have used opts="IGNORE_XY_AXIS_NAME_CHECKS=YES"
			test <- try(r <- .ncdf_extent(r, f), silent=TRUE)
			if (inherits(test, "try-error")) {
				warn("rast", "GDAL did not find an extent. Cells not equally spaced?")
			}
		}
		r <- messages(r, "rast")

		if (!is.null(lyrs)) {
			r <- r[[lyrs]]
		} 
		if (!is.null(win)) {
			e <- ext(win)
			e <- align(e, r, snap=snap)
			window(r) <- e
		}
		r
	}
)


multi <- function(x, subds=0, xyz=3:1, drivers=NULL, opts=NULL) {

	noflip <- FALSE

	x <- trimws(x)
	x <- x[x!=""]
	if (length(x) == 0) {
		error("rast,character", "provide a valid filename")
	}
	r <- methods::new("SpatRaster")
	f <- .fullFilename(x)
	if (is.null(opts)) opts <- ""[0]
	if (is.null(drivers)) drivers <- ""[0]
	if (length(subds) == 0) subds = 1
	subds <- subds[1]

	if (is.character(subds)) {
		r@pntr <- SpatRaster$new(f, -1, subds, TRUE, drivers, opts, xyz-1, isTRUE(noflip), isTRUE(guessCRS))
	} else {
		r@pntr <- SpatRaster$new(f, subds-1, ""[0], TRUE, drivers, opts, xyz-1, isTRUE(noflip), isTRUE(guessCRS))
	}
	if (r@pntr$getMessage() == "ncdf extent") {
		test <- try(r <- .ncdf_extent(r), silent=TRUE)
		if (inherits(test, "try-error")) {
			warn("rast", "GDAL did not find an extent. Cells not equally spaced?")
		}
	}
	r <- messages(r, "rast")

	if (crs(r) == "") {
		if (is.lonlat(r, perhaps=TRUE, warn=FALSE)) {
			crs(r) <- "OGC:CRS84"
		}
	}
	r
}


setMethod("rast", signature(x="SpatRaster"),
	function(x, nlyrs=nlyr(x), names, vals, keeptime=TRUE, keepunits=FALSE, props=FALSE, tags=FALSE) {
		if (inherits(nlyrs, "SpatRaster")) {
			error("rast", "use 'c()' to combine SpatRasters")
		}
		x@pntr <- x@pntr$geometry(nlyrs, props, keeptime, keepunits, tags)
		x <- messages(x, "rast")
		if (!missing(names)) {
			if (length(names) == nlyr(x)) names(x) <- names
		}
		if (!missing(vals)) {
			values(x) <- vals
		}
		x
	}
)


setMethod("rast", signature(x="SpatRasterDataset"),
	function(x) {
		if (length(x) == 0) {
			error("rast", "empty SpatRasterDataset")
		} else if (length(x) == 1) {
			x[1]
		} else {
			r <- methods::new("SpatRaster")
			r@pntr <- x@pntr$collapse()
			nms <- names(x)
			if (any(nms != "")) {
				names(r) <- paste(rep(nms, nlyr(x)), names(r), sep="_")
			}
			r
		}
	}
)


setMethod("rast", signature(x="array"),
	function(x, crs="", extent=NULL) {
		dims <- dim(x)
		if (length(dims) < 3) {
			error("rast,array", "cannot handle an array with less than 3 dimensions")
		}
		if (length(dims) > 3) {
			if (length(dims) == 4) {
				if (dims[4] == 1) {
					x <- x[,,,1]
				} else {
					error("rast,array", "rast cannot handle an array with 4 dimensions (try 'sds')")
				}
			} else {
				error("rast,array", "cannot handle an array with more than 3 dimensions")
			}
		}
		r <- methods::new("SpatRaster")
		if (!is.null(extent)) {
			e <- as.vector(extent)
		} else {
			e <- c(0, dims[2], 0, dims[1])
		}
		crs <- character_crs(crs, "rast")
		r@pntr <- SpatRaster$new(dims, e, crs)
		values(r) <- x
		messages(r, "rast")
	}
)



setMethod("rast", signature(x="ANY"),
	function(x, ...) {
		if (inherits(x, "sf")) {
			out <- rast(ext(x), ...)
			if (is.null(list(...)$crs)) {
				sfi <- attr(x, "sf_column")
				crs(out, warn=FALSE) <- attr(x[[sfi]], "crs")$wkt
			}
		} else {
			out <- methods::as(x, "SpatRaster")
		}
		#g <- gc()
		out
	}
)


.rastFromXYZ <- function(xyz, digits=6, crs="", extent=NULL) {


	ln <- colnames(xyz)
	## xyz might not have colnames, or might have "" names
	if (is.null(ln)) ln <- rep("", ncol(xyz))
	if (any(nchar(ln) < 1)) ln <- make.names(ln)
	if (inherits(xyz, "data.frame")) {
		xyz <- as.matrix(xyz)
		xyz <- matrix(as.numeric(xyz), ncol=ncol(xyz), nrow=nrow(xyz))
	}
	
	x <- sort(unique(xyz[,1]))
#	x <- sort(unique(round(xyz[,1], digits+2)))
	
	if (length(x) == 1) {
		error("rast", "cannot create a raster geometry from a single x coordinate")
	}
	dx <- x[-1] - x[-length(x)]

	rx <- min(dx)
	for (i in 1:5) {
		rx <- rx / i
		q <- sum(round(dx / rx, digits=digits) %% 1)
		if ( q == 0 ) {
			break
		}
	}
	if ( q > 0 ) {
		error("raster,matrix(xyz)", "x cell sizes are not regular")
	}

	y <- sort(unique(xyz[,2]))
#	y <- sort(unique(round(xyz[,2], digits+2)))
	if (length(y) == 1) {
		error("rast", "cannot create a raster geometry from a single y coordinate")
	}
	dy <- y[-1] - y[-length(y)]

	ry <- min(dy)
	for (i in 1:5) {
		ry <- ry / i
		q <- sum(round(dy / ry, digits=digits) %% 1)
		if ( q == 0 ) {
			break
		}
	}
	if ( q > 0 ) {
		error("raster,matrix(xyz)", "y cell sizes are not regular")
	}

	minx <- min(x) - 0.5 * rx
	maxx <- max(x) + 0.5 * rx
	miny <- min(y) - 0.5 * ry
	maxy <- max(y) + 0.5 * ry

	d <- dim(xyz)
	r <- rast(xmin=minx, xmax=maxx, ymin=miny, ymax=maxy, crs=crs, nlyrs=max(1, d[2]-2))
	res(r) <- c(rx, ry)
	ext(r) <- round(ext(r), digits+2)
	cells <- cellFromXY(r, xyz[,1:2])
	if (d[2] > 2) {
		names(r) <- ln[-c(1:2)]
		v <- try( matrix(NA, nrow=ncell(r), ncol= nlyr(r)) )
		if (inherits(v, "try-error")) {
			error(paste("cannot make matrix with ", ncell(r), " rows"))
		}
		v[cells, ] <- xyz[, -c(1:2)]
		values(r) <- v
	}

	if (!is.null(extent)) {
		r <- extend(r, extent)
		r <- crop(r, extent)
	}

	return(r)
}


.rastFromXYLZ <- function(x, digits=6, crs="", extent=NULL) {
	if (ncol(x) != 4) {
		error("rast", "a 'xylz' structure must have 4 columns")
	}
	nms <- names(x)
	names(x)[1:3] <- c("x", "y", "l")
	x <- as.data.frame(x) # so that this also works for tibble #1582
	w <- stats::reshape(x, timevar="l", idvar=c("x", "y"), direction="wide")
	w <- rast(w, type="xyz", digits=digits, crs=crs, extent=extent)
	names(w) <- gsub(paste0(nms[4], "."), "", names(w))
	if (inherits(x[,3], "Date") || inherits(x[,3], "POSIXlt") || inherits(x[,3], "POSIXct")) {
		time(w) <- unique(x[,3])
		names(w) <- paste0(nms[4], ".", 1:nlyr(w))
	} else if (inherits(x[,3], "numeric")) {
		u <- unique(x[,3])
		if (all(u == trunc(u))) {
			su <- sort(u)
			if ((su[1] == 1) && (su[length(su)] == length(su))) {
				if (!all(su == u)) {
					w <- w[[order(u)]]
				}
			}
		}
	}
	w
}

	

setMethod("rast", signature(x="matrix"),
	function(x, type="", crs="", digits=6, extent=NULL) {
		stopifnot(prod(dim(x)) > 0)
		if (type == "xyz") {
			r <- .rastFromXYZ(x, crs=crs, digits=digits, extent=extent)
		} else if (type == "xylz") {
			r <- .rastFromXYLZ(x, crs=crs, digits=digits, extent=extent)
		} else if (type != "") {
			error("rast", 'argument type should be one of "", "xyz", or "xylz"')
		} else {
			if (is.null(extent)) {
				r <- rast(nrows=nrow(x), ncols=ncol(x), extent=ext(c(0, ncol(x), 0, nrow(x))), crs=crs)
			} else {
				r <- rast(nrows=nrow(x), ncols=ncol(x), crs=crs, extent=extent)
			}
			values(r) <- as.vector(t(x))
		}
		messages(r, "rast")
	}
)


setMethod("rast", signature(x="data.frame"),
	function(x, type="xyz", crs="", digits=6, extent=NULL) {
		if (type == "xyz") {
			.rastFromXYZ(x, crs=crs, digits=digits, extent=extent)
		} else if (type == "xylz") {
			r <- .rastFromXYLZ(x, crs=crs, digits=digits, extent=extent)
		} else if (type != "") {
			error("rast", 'argument type should be one of "", "xyz", or "xylz"')
		} else {
			rast(as.matrix(x), type=type, crs=crs, digits=digits, extent=extent)
		}
	}
)


setMethod("rast", signature(x="stars"),
	function(x) {
		x <- from_stars(x)
		if (inherits(x, "SpatRasterDataset")) {
			rast(x)
		} else {
			x
		}
	}
)

setMethod("rast", signature(x="stars_proxy"),
	function(x) {
		x <- from_stars(x)
		if (inherits(x, "SpatRasterDataset")) {
			rast(x)
		} else {
			x
		}
	}
)

setMethod("NAflag<-", signature(x="SpatRaster"),
	function(x, value)  {
		value <- as.numeric(value)
		if (!(x@pntr$setNAflag(value))) {
			error("NAflag<-", "cannot set this value")
		}
		x
	}
)

setMethod("NAflag", signature(x="SpatRaster"),
	function(x)  {
		x@pntr$getNAflag()
	}
)


